home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 2000 August: Tool Chest / Dev.CD Aug 00 TC Disk 1.toast / pc / sample code / contributed / waste / waste 1.3 / source / welinelayout.c < prev    next >
Encoding:
C/C++ Source or Header  |  2000-06-23  |  14.8 KB  |  534 lines

  1. /*
  2.  *    WELineLayout.c
  3.  *
  4.  *    WASTE PROJECT
  5.  *  Line Layout, Getting and Setting Variables, etc.
  6.  *
  7.  *  Copyright (c) 1993-1998 Marco Piovanelli
  8.  *    All Rights Reserved
  9.  *
  10.  *  C port by Dan Crevier
  11.  *
  12.  */
  13.  
  14.  
  15. #include "WASTEIntf.h"
  16.  
  17. INLINE pascal void _WERemoveLine(SInt32 lineIndex, WEPtr pWE)
  18. {
  19.     // remove the line
  20.     _WESplice((Handle) pWE->hLines, nil, - sizeof(WELineRec), lineIndex * sizeof(WELineRec));
  21.  
  22.     // decrement line count
  23.     pWE->nLines--;
  24. }
  25.  
  26. pascal OSErr _WEInsertLine(SInt32 lineIndex, const WELineRec *pLine, WEPtr pWE)
  27. {
  28.     // insert the specified entry in the line array
  29.     OSErr err;
  30.  
  31.     // do the insertion
  32.     if ((err = _WESplice((Handle) pWE->hLines, pLine, sizeof(WELineRec), lineIndex * sizeof(WELineRec))) != noErr)
  33.     {
  34.         return err;
  35.     }
  36.  
  37.     // increment line count
  38.     pWE->nLines++;
  39.     return noErr;
  40. }
  41.  
  42. pascal void _WEBumpOrigin(SInt32 lineIndex, SInt32 deltaOrigin, WEPtr pWE)
  43. {
  44.     WELineRec *pLine = *pWE->hLines + lineIndex;
  45.     SInt32 nLines = pWE->nLines;
  46.  
  47.     // loop through the line run array adjusting the lineOrigin fields
  48.     for ( ; lineIndex <= nLines; lineIndex++ )
  49.     {
  50.         pLine->lineOrigin += deltaOrigin;
  51.         pLine++;
  52.     }
  53. }
  54.  
  55. pascal SInt32 _WEFindLineBreak(SInt32 lineStart, WEHandle hWE)
  56. {
  57.     // Find where to break the line beginning at lineStart
  58.     // the current graphics port must be already set up correctly
  59.  
  60.     WEPtr pWE = *hWE;    // assume WE record is already locked
  61.     Ptr pText;
  62.     SInt32 offset, breakOffset;
  63.     SInt32 textLength;
  64.     SInt32 remainingLength;
  65.     SInt32 segmentStart, segmentEnd;
  66.     SInt32 runIndex;
  67.     WERunInfo runInfo;
  68.     Fixed pixelWidth;
  69.     ScriptCode script, previousScript;
  70.     Boolean isBreak = false;
  71.  
  72.     offset = lineStart;
  73.     pText = *pWE->hText + offset;
  74.     remainingLength = pWE->textLength - offset;
  75.  
  76.     // find the style run index corresponding to the first segment on this line
  77.     runIndex = WEOffsetToRun(offset, hWE);
  78.  
  79.     // initialize pixelWidth to the width of the destination rectangle, as a Fixed quantity
  80.     pixelWidth = BSL((pWE->destRect.right - pWE->destRect.left), 16);
  81.  
  82.     // STYLE SEGMENT LOOP
  83.     do
  84.     {
  85.  
  86.         // get style run information for the current style run
  87.         _WEGetIndStyle(runIndex, &runInfo, hWE);
  88.         runIndex++;
  89.  
  90.         // set text attributes in the graphics port
  91.         TextFont(runInfo.runAttrs.runStyle.tsFont);
  92.         TextFace(runInfo.runAttrs.runStyle.tsFace);
  93.         TextSize(runInfo.runAttrs.runStyle.tsSize);
  94.  
  95.         // if we're handling multiscript text, keep track of script boundaries
  96.         if (BTST(pWE->flags, weFNonRoman))
  97.         {
  98.             // what is the script for this segment?
  99.             script = FontToScript(runInfo.runAttrs.runStyle.tsFont);
  100.  
  101.             // have we crossed a script run boundary in the middle of a line?
  102.             if ((runInfo.runStart > offset) && (script != previousScript))
  103.             {
  104.                 // leave behind the all previous segments on this line
  105.                 offset = runInfo.runStart;
  106.                 pText = *pWE->hText + offset;
  107.                 remainingLength = pWE->textLength - offset;
  108.             }
  109.             previousScript = script;
  110.         } // if non-Roman
  111.  
  112.         // we'll pass textLength as the second parameter to the line break hook
  113.         // although this parameter is declared as a long, StyledLineBreak uses only
  114.         // the low word, so make sure it doesn't trespass the 32,767 byte threshold!
  115.         textLength = _WEPinInRange(remainingLength, 0, SHRT_MAX);
  116.  
  117.         // calculate segmentStart and segmentEnd relative to offset
  118.         segmentStart = _WEPinInRange(runInfo.runStart - offset, 0, textLength);
  119.         segmentEnd = _WEPinInRange(runInfo.runEnd - offset, 0, textLength);
  120.  
  121.         // set breakOffset to a non-zero value for the first script run on the line,
  122.         // set it to zero for all subsequent script runs
  123.         breakOffset = (offset == lineStart);
  124.  
  125. #if WASTE_OBJECTS
  126.         if (runInfo.runAttrs.runStyle.tsObject != nil)
  127.         {
  128.             // EMBEDDED OBJECT
  129.             // subtract object width from pixelWidth
  130.             pixelWidth -= BSL((*runInfo.runAttrs.runStyle.tsObject)->objectSize.h, 16);
  131.  
  132.             // stop looping if pixelWidth has gone negative
  133.             isBreak = (pixelWidth < 0);
  134.             breakOffset = isBreak ? segmentStart : segmentEnd;
  135.         }
  136.         else
  137. #endif
  138.         {
  139.             // REGULAR TEXT
  140.             isBreak = (CallWELineBreakProc(pText, textLength, segmentStart, segmentEnd,
  141.                 &pixelWidth, &breakOffset, hWE, pWE->lineBreakHook) != smBreakOverflow);
  142.         }
  143.  
  144.         // break the line anyway when we reach the end of the text
  145.         if ((segmentEnd >= remainingLength) || (runIndex >= pWE->nRuns))
  146.         {
  147.             isBreak = true;
  148.         }
  149.     } while (!isBreak);
  150.  
  151.     // return the offset from lineStart to the break point
  152.     return (offset - lineStart) + breakOffset;
  153. }
  154.  
  155. pascal void _WECalcHeights(SInt32 rangeStart, SInt32 rangeEnd, SInt16 *lineAscent, SInt16 *lineDescent,
  156.         WEHandle hWE)
  157. {
  158.     // Find the maximum ascent and descent values between rangeStart and rangeEnd
  159.     // the WE record must be already locked
  160.     // the current graphics port must be already set up correctly
  161.  
  162.     SInt32 runIndex;
  163.     WERunInfo runInfo;
  164.     SInt16 runAscent, runDescent;
  165.  
  166.     *lineAscent = 1;
  167.     *lineDescent = 0;
  168.  
  169.     // find the style run index corresponding to the first segment on this line
  170.     runIndex = WEOffsetToRun(rangeStart, hWE);
  171.  
  172.     // STYLE SEGMENT LOOP
  173.     do
  174.     {
  175.         // get style run information for the current style run
  176.         _WEGetIndStyle(runIndex, &runInfo, hWE);
  177.         runIndex++;
  178.  
  179.         // calculate ascent and descent (actually, descent + leading) values for this style run
  180.  
  181. #if WASTE_OBJECTS
  182.         if (runInfo.runAttrs.runStyle.tsObject != nil)
  183.         {
  184.             // EMBEDDED OBJECT
  185.             runAscent = (*runInfo.runAttrs.runStyle.tsObject)->objectSize.v;
  186.             runDescent = 0;
  187.         }
  188.         else
  189. #endif
  190.         {
  191.             // REGULAR TEXT
  192.             runAscent = runInfo.runAttrs.runAscent;
  193.             runDescent = runInfo.runAttrs.runHeight - runAscent;
  194.         }
  195.  
  196.         // save the maximum values in lineAscent and lineDescent
  197.         if (runAscent > *lineAscent)
  198.         {
  199.             *lineAscent = runAscent;
  200.         }
  201.  
  202.         if (runDescent > *lineDescent)
  203.         {
  204.             *lineDescent = runDescent;
  205.         }
  206.  
  207.         // keep looping until we reach rangeEnd
  208.     } while (runInfo.runEnd < rangeEnd);
  209. }
  210.  
  211. pascal OSErr _WERecalBreaks(SInt32 *startLine, SInt32 *endLine, WEHandle hWE)
  212. {
  213.     // Recalculates line breaks, line heights and ascents for all the text or for a portion of it.
  214.     // On entry, startLine and endLine define a range of lines to recalculate.
  215.     // On exit, startLine to endLine defines the range of lines actually recalculated
  216.     // the WE record must already be locked
  217.  
  218.     WEPtr pWE = *hWE;
  219.     WELineRec *pLine;
  220.     WELineRec lineInfo, oldLineInfo;
  221.     SInt32 lineIndex;
  222.     SInt32 recalThreshold;
  223.     SInt32 lineOffset;
  224.     SInt16 lineAscent, lineDescent;
  225.     SInt32 textHeight;
  226.     Boolean saveTextLock;
  227.     QDEnvironment saveEnvironment;
  228.     OSErr err = noErr;
  229.  
  230.     // lock the text
  231.     saveTextLock = _WESetHandleLock(pWE->hText, true);
  232.  
  233.     // find the character offset that must be necessarily reached before we can
  234.     // even consider the possibility of stopping the recalculation process
  235.     // this offset, recalThreshold, is the last character on endLine _before_ recalculation
  236.     lineIndex = _WEPinInRange(*endLine, 0, pWE->nLines - 1);
  237.     recalThreshold = (*pWE->hLines)[lineIndex + 1].lineStart;
  238.  
  239.     // we start recalculating line breaks from the line actually _preceding_ startLine,
  240.     // since editing startLine may cause part of its text to fit on the preceding line
  241.     lineIndex = _WEPinInRange(*startLine - 1, 0, pWE->nLines - 1);
  242.  
  243.     // find where in the text recalculation should begin
  244.     lineInfo = (*pWE->hLines)[lineIndex];
  245.  
  246.     // save the Quickdraw environment
  247.     _WESaveQDEnvironment(pWE->port, false, &saveEnvironment);
  248.  
  249.     // MAIN LINE BREAKING LOOP
  250.     do
  251.     {
  252.         // find where to break the current line
  253.         lineOffset = _WEFindLineBreak(lineInfo.lineStart, hWE);
  254.  
  255.         // make sure we advance at least by one character (unless we reached the end of text)
  256.         if ((lineOffset <= 0) && (lineInfo.lineStart < pWE->textLength))
  257.         {
  258.             lineOffset = 1;
  259.         }
  260.  
  261.         // calculate ascent and descent values for this line
  262.         _WECalcHeights(lineInfo.lineStart, lineInfo.lineStart + lineOffset, &lineAscent, &lineDescent, hWE);
  263.  
  264.         // save the maximum line ascent for this line in the line array
  265.         pLine = *pWE->hLines + lineIndex;
  266.         pLine->lineAscent = lineAscent;
  267.  
  268.         // increment counters (go to the next line array entry)
  269.         lineIndex++;
  270.         lineInfo.lineStart += lineOffset;
  271.         lineInfo.lineOrigin += (lineAscent + lineDescent);
  272.         pLine++;
  273.  
  274.         // compare the newly calculated line start with the old value
  275.         // if the new line start comes before the old line start, insert a new element
  276.         oldLineInfo = *pLine;
  277.         if ((lineIndex > pWE->nLines) || (lineInfo.lineStart < oldLineInfo.lineStart))
  278.         {
  279.             if ((err = _WEInsertLine(lineIndex, &lineInfo, pWE)) != noErr)
  280.             {
  281.                 goto cleanup;
  282.             }
  283.         }
  284.         else
  285.         {
  286.             // overwrite the old element
  287.             pLine->lineStart = lineInfo.lineStart;
  288.             pLine->lineOrigin = lineInfo.lineOrigin;
  289.  
  290.             // remove all further elements which have a lineStart field
  291.             // less than or equal to the current one
  292.             while((lineIndex < pWE->nLines) && (lineInfo.lineStart >= (pLine + 1)->lineStart))
  293.                 _WERemoveLine(lineIndex + 1, pWE);
  294.  
  295.             // if the new line start is the same as the old one...
  296.             if (lineInfo.lineStart == oldLineInfo.lineStart)
  297.             {
  298.                 // ...and recalThreshold has been reached, we can stop recalculating line breaks
  299.                 if (lineInfo.lineStart >= recalThreshold)
  300.                 {
  301.                     // although line breaks need not be changed from lineIndex on,
  302.                     // the lineOrigin fields may need to be changed
  303.                     if (lineInfo.lineOrigin != oldLineInfo.lineOrigin)
  304.                     {
  305.                         _WEBumpOrigin(lineIndex + 1, lineInfo.lineOrigin - oldLineInfo.lineOrigin, pWE);
  306.                     }
  307.  
  308.                     // exit from the line breaking loop
  309.                     goto cleanup;
  310.                 }
  311.             }
  312.             else
  313.             {
  314.                 // otherwise, the new line start comes after the old line start...
  315.                 // if the current line is the one preceding startLine, warn our caller about this
  316.                 if ((lineIndex > 0) && (lineIndex == *startLine))
  317.                 {
  318.                     *startLine = lineIndex - 1;
  319.                 }
  320.             }
  321.         }
  322.     } while(lineInfo.lineStart < pWE->textLength);
  323.  
  324. cleanup:
  325.     // calculate total text height
  326.     textHeight = WEGetHeight(0, pWE->nLines, hWE);
  327.  
  328.     // quirk: if the last character in the text is a carriage return, the caret appears
  329.     // below the last line, so in this case we need to add the extra height to textHeight
  330.     if (WEGetChar(pWE->textLength - 1, hWE) == kEOL)
  331.     {
  332.         textHeight += WEGetHeight(pWE->nLines - 1, pWE->nLines, hWE);
  333.     }
  334.  
  335.     // if total text height has changed, remember to call the scroll callback later
  336.     if (textHeight != pWE->destRect.bottom - pWE->destRect.top)
  337.     {
  338.         BSET(pWE->flags, weFDestRectChanged);
  339.     }
  340.  
  341.     // set destRect.bottom to destRect.top + total text height
  342.     pWE->destRect.bottom = pWE->destRect.top + textHeight;
  343.  
  344.     // return through endLine the index of the last line affected by recalculation
  345.     *endLine = lineIndex - 1;
  346.  
  347.     // make sure startLine isn't greater than endLine
  348.     if (*startLine > *endLine)
  349.     {
  350.         *startLine = *endLine;
  351.     }
  352.  
  353.     // unlock the text
  354.     _WESetHandleLock(pWE->hText, saveTextLock);
  355.  
  356.     // restore the Quickdraw environment
  357.     _WERestoreQDEnvironment(&saveEnvironment);
  358.  
  359.     // return result code
  360.     return err;
  361. }
  362.  
  363. static Boolean SLCalcSlop
  364.     (
  365.         WELineRec *pLine,
  366.         const WERunAttributes *pAttrs,
  367.         Ptr pSegment,
  368.         SInt32 segmentStart,
  369.         SInt32 segmentLength,
  370.         JustStyleCode styleRunPosition,
  371.         WEHandle hWE,
  372.         void *callbackData
  373.     )
  374. {
  375. #if !WASTE_OBJECTS
  376.     #pragma unused(pAttrs)
  377. #endif
  378.     struct SLCalcSlopData *cd = (struct SLCalcSlopData *) callbackData;
  379.     SInt16 segmentWidth;
  380.     Fixed segmentProportion;
  381.     Boolean isEndOfLine;
  382.  
  383.     // see if this text segment ends with a carriage return, or if we've reached the
  384.     // end of the text (in which case we don't want any justification to take place)
  385.     isEndOfLine = (segmentStart + segmentLength >= (*hWE)->textLength) ||
  386.                   (pSegment [ segmentLength - 1 ] == kEOL);
  387.  
  388.     // if this is the first segment on the line, reset line totals
  389.     if (IS_LEFTMOST_RUN(styleRunPosition))
  390.     {
  391.         cd->totalSlop = cd->lineWidth;
  392.         cd->totalProportion = 0;
  393.     }
  394.  
  395. #if WASTE_OBJECTS
  396.     if (pAttrs->runStyle.tsObject != nil)
  397.     {
  398.         // EMBEDDED OBJECT
  399.         // segment width is just object width; no extra space can be applied for justification
  400.         segmentWidth = (*pAttrs->runStyle.tsObject)->objectSize.h;
  401.         segmentProportion = 0;
  402.     }
  403.     else
  404. #endif
  405.     {
  406.         // REGULAR TEXT
  407.  
  408.         // if this is the last segment on the line, strip trailing spaces
  409.         if (IS_RIGHTMOST_RUN(styleRunPosition))
  410.         {
  411.             segmentLength = VisibleLength(pSegment, segmentLength);
  412.         }
  413.  
  414.         // measure this segment
  415.         segmentWidth = TextWidth(pSegment, 0, segmentLength);
  416.  
  417.         // calculate the proportion of extra space to apply to this text segment
  418.         segmentProportion = PortionLine(pSegment, segmentLength,
  419.                 styleRunPosition, kOneToOneScaling, kOneToOneScaling);
  420.     }
  421.  
  422.     // keep track of line totals
  423.     cd->totalSlop -= segmentWidth;
  424.     cd->totalProportion += segmentProportion;
  425.  
  426.     // if this is the last segment on the line, save values in the line array
  427.     if (IS_RIGHTMOST_RUN(styleRunPosition))
  428.     {
  429.         // make sure slop is non-negative
  430.         if (cd->totalSlop < 0)
  431.         {
  432.             cd->totalSlop = 0;
  433.         }
  434.         pLine->lineSlop = cd->totalSlop;
  435.         pLine->lineJustAmount = isEndOfLine ? 0 : FixDiv(BSL(cd->totalSlop, 16), cd->totalProportion);
  436.     }
  437.     return false;    // keep looping
  438. }
  439.  
  440. pascal void _WERecalSlops(SInt32 firstLine, SInt32 lastLine, WEHandle hWE)
  441. {
  442.     // Calculates the lineSlop and lineJustAmount fields
  443.     // of the line array for the specified lines
  444.  
  445.     WEPtr pWE = *hWE;
  446.     struct SLCalcSlopData cd;
  447.  
  448.     // we only need to bother if the user isn't using left justification
  449.     if (pWE->alignment == weFlushLeft)
  450.     {
  451.         return;
  452.     }
  453.  
  454.     // calculate slop and normalized slop proportion for all lines
  455.     cd.lineWidth = pWE->destRect.right - pWE->destRect.left;
  456.     _WESegmentLoop(firstLine, lastLine, SLCalcSlop, &cd, hWE);
  457. }
  458.  
  459. pascal void WESetAlignment(WEAlignment alignment, WEHandle hWE)
  460. {
  461.     WEPtr pWE;
  462.     WEAlignment oldAlignment;
  463.     Boolean saveWELock;
  464.  
  465.     // lock the WE record
  466.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  467.     pWE = *hWE;
  468.     oldAlignment = pWE->alignment;
  469.  
  470.     if ((alignment >= weFlushLeft) && (alignment <= weJustify) && (alignment != oldAlignment))
  471.     {
  472.         // hide the caret if it's showing
  473.         if (BTST(pWE->flags, weFCaretVisible))
  474.         {
  475.             _WEBlinkCaret(hWE);
  476.         }
  477.  
  478.         // change the alignment
  479.         pWE->alignment = alignment;
  480.  
  481.         if (! BTST(pWE->features, weFInhibitRecal))
  482.         {
  483.             // if the text was left-aligned, then we haven't been bothering till now,
  484.             // so we have to recalc the whole document
  485.             if (oldAlignment == weFlushLeft)
  486.             {
  487.                 _WERecalSlops(0, pWE->nLines - 1, hWE);
  488.             }
  489.  
  490.             if (! BTST(pWE->features, weFInhibitRedraw))
  491.             {
  492.                 // redraw the view rectangle
  493.                 WEUpdate(nil, hWE);
  494.             }
  495.         }
  496.     }
  497.  
  498.     // unlock the WE record
  499.     _WESetHandleLock((Handle) hWE, saveWELock);
  500. }
  501.  
  502. pascal void WESetDirection(WEDirection direction, WEHandle hWE)
  503. {
  504.     WEPtr pWE;
  505.     WEDirection oldDirection;
  506.     Boolean saveWELock;
  507.  
  508.     // lock the WE record
  509.     saveWELock = _WESetHandleLock((Handle) hWE, true);
  510.     pWE = *hWE;
  511.     oldDirection = pWE->direction;
  512.  
  513.     if ((direction >= weDirRightToLeft) && (direction <= weDirDefault) && (direction != oldDirection))
  514.     {
  515.         // hide the caret if it's showing
  516.         if (BTST(pWE->flags, weFCaretVisible))
  517.         {
  518.             _WEBlinkCaret(hWE);
  519.         }
  520.  
  521.         // change the direction
  522.         pWE->direction = direction;
  523.  
  524.         if (! (pWE->features & ((1L << weFInhibitRecal) | (1L << weFInhibitRedraw))))
  525.         {
  526.             // redraw the view rectangle
  527.             WEUpdate(nil, hWE);
  528.         }
  529.     }
  530.  
  531.     // unlock the WE record
  532.     _WESetHandleLock((Handle) hWE, saveWELock);
  533. }
  534.